import hashlib
import random
import string
import time
from decimal import Decimal

import requests
from bs4 import BeautifulSoup, element
from django.db import transaction
from django.db.models import F

import settings
from social.exceptions import CodeException
from social.payment.models import Order, Wallet, WalletRecord, Product, VIPHistory


class AbstractPayments:
    available_platforms = []

    def mark_order_payed(self, out_trade_no, callback_data):
        order = Order.objects.get(out_trade_no=out_trade_no)
        if order.status == 2:
            return
        with transaction.atomic():
            order.status = 2
            order.callback = str(callback_data)
            order.save(update_fields=['status', 'callback'])

            product = order.product
            if product.type == Product.COIN:
                # 更新钱包
                wallet, _ = Wallet.objects.get_or_create(user=order.user)
                wallet.balance = F('balance') + product.coin
                wallet.total_balance = F('total_balance') + product.coin
                wallet.save(update_fields=['balance', 'total_balance'])
                # 更新钱包记录
                WalletRecord.objects.create(user=order.user, type=WalletRecord.COIN, title='购买秀币',
                                            change=product.coin)
            elif product.type == Product.VIP:
                VIPHistory.objects.create(user=order.user, product=product)
                # 用户购买vip
                order.user.buy_vip(product)


class WxPay(AbstractPayments):
    appid = 'wx94e4887dd2de7cf2'
    mch_id = "1654331361"
    notify_url = "http://xiuyu-api.zhangleapp.com:9001/api/payment/wxpay_callback/"
    gatway = "https://api.mch.weixin.qq.com/pay/unifiedorder"
    api_key = '4a261be3729ca9858c149d305f1b9852'

    available_platforms = ['app', 'js', 'h5']

    @property
    def nonce_str(self):
        return "".join(random.sample(string.ascii_letters + string.digits, 10))

    def sign(self, data: dict):
        keys = sorted(data.keys())
        to_sign = []
        for k in keys:
            value = data[k]
            if value:
                to_sign.append(f'{k}={value}')
            else:
                data[k] = ''
        to_sign.append(f"key={self.api_key}")
        to_sign_str = "&".join(to_sign)
        sign = hashlib.md5(to_sign_str.encode('utf-8')).hexdigest()
        return sign.upper()

    def dict_to_xml(self, data: dict):
        xml = "<xml>"
        for k in data:
            xml += f"<{k}>{data[k]}</{k}>"
        xml += "</xml>"
        return xml

    def _trade_response(self, _data) -> BeautifulSoup:
        data = {}
        data['appid'] = self.appid
        data['mch_id'] = self.mch_id
        data['nonce_str'] = self.nonce_str
        data['body'] = _data['subject']
        data['out_trade_no'] = _data['out_trade_no']
        data['fee_type'] = "CNY"
        data['total_fee'] = int(_data['total_amount'])
        data['notify_url'] = self.notify_url
        data['trade_type'] = _data['trade_type']

        data['sign'] = self.sign(data)
        params = self.dict_to_xml(data)

        response = requests.post(self.gatway, data=params.encode('utf-8'))
        return BeautifulSoup(response.content, features='xml')

    def trade_app(self, _data) -> dict:
        bs = self._trade_response(_data)
        if bs.result_code and bs.result_code.text == "SUCCESS" and bs.return_msg.text == 'OK':
            data = {'appid': bs.appid.text,
                    'partnerid': bs.mch_id.text,
                    'prepayid': bs.prepay_id.text,
                    'package': 'Sign=WXPay',
                    'timestamp': str(int(time.time())),
                    'noncestr': bs.nonce_str.text}
            data['sign'] = self.sign(data)
            return data
        else:
            raise CodeException(msg=bs.return_msg.text)

    def handle_callback(self, data):
        bs = BeautifulSoup(data, features='xml')
        dic = {}
        for tag in bs.xml.children:
            if isinstance(tag, element.Tag):
                dic[tag.name] = tag.text
        sign = dic.pop('sign')
        my_sign = self.sign(dic)

        if sign != my_sign or dic['result_code'] != 'SUCCESS':
            raise CodeException(msg="验证签名失败")
        order_id = dic['out_trade_no']
        self.mark_order_payed(order_id, data)
        return True
